#include <assert.h>

#include "Image.h"


//////////////////////////////////////////////////////////////////
// Image Object													//
//////////////////////////////////////////////////////////////////

// Constructor for image. This constructor can load PNG, JPEG, 
// TIFF, BMP and GIF images.
Image::Image():CImage(){}

// Copy constructor for image. Note that this is extremely
// important as the base class has no copy constructor whatsoever!
Image::Image(const Image &init)
{
	// Create space for the image.
	Create(init.GetWidth(),init.GetHeight(),init.GetBPP());

	// The pitch gives the distance between the first pixels of 
	// two consecutive lines in bytes. A negative pitch means that 
	// the image is stored starting at the bottom left, while a 
	// positive pitch means the image is stored starting from the
	// top left corner.
	int buffer = GetPitch();
	int init_buffer = init.GetPitch();
	BYTE* pixel = NULL;
	BYTE* init_pixel = NULL;
	if(buffer > 0)
		pixel = static_cast<BYTE*>(GetPixelAddress(0,0));
	else
		pixel = static_cast<BYTE*>(GetPixelAddress(0,GetHeight()-1));
	if(init_buffer > 0)
		init_pixel = static_cast<BYTE*>(const_cast<void*>(init.GetPixelAddress(0,0)));
	else
		init_pixel = static_cast<BYTE*>(const_cast<void*>(init.GetPixelAddress(0,GetHeight()-1)));

	// Next, we check if there is any extra space after each line 
	// of the image. Note that the same variables from before are 
	// used, but in a different way.
	buffer = abs(buffer) - (GetWidth()*(GetBPP()>>3));
	init_buffer = abs(init_buffer) - (init.GetWidth()*(init.GetBPP()>>3));

	// A case statement is used for the copying.
	int BPP = GetBPP()>>3;
	switch(BPP)
	{
		// This statement represents the case where the image is
		// greyscale (only one colour channel).
		case 1:
			for(int i=0; i<GetHeight(); i++)
			{
				for(int j=0; j<GetWidth(); j++)
					*pixel++ = *init_pixel++;
				for(int k=0; k<buffer; k++)
					pixel++;
				for(int k=0; k<init_buffer; k++)
					init_pixel++;
			}
			// For greyscale images, a palette must be specified, 
			// otherwise trying to save the image won't work.
			RGBQUAD colours[256];
			for(int i=0; i<256; i++)
			{
				colours[i].rgbRed = i;
				colours[i].rgbGreen = i;
				colours[i].rgbBlue = i;
			}
			SetColorTable(0,256,colours);
			break;
		// This statement represents the case where the image is 
		// colour (3 channels).
		case 3:
			for(int i=0; i<GetHeight(); i++)
			{
				for(int j=0; j<GetWidth(); j++)
				{
					*pixel++ = *init_pixel++;
					*pixel++ = *init_pixel++;
					*pixel++ = *init_pixel++;
				}
				for(int k=0; k<buffer; k++)
					pixel++;
				for(int k=0; k<init_buffer; k++)
					init_pixel++;
			}
			break;
		default:
			for(int i=0; i<GetHeight(); i++)
			{
				for(int j=0; j<GetWidth(); j++)
				{
					for(int k=0; k<BPP; k++)
						*pixel++ = *init_pixel++;
				}
				for(int k=0; k<buffer; k++)
					pixel++;
				for(int k=0; k<init_buffer; k++)
					init_pixel++;
			}
	}
}

// Destructor for image.
Image::~Image()
{
	Destroy();
}

// This function converts an image to greyscale and overwrites the
// old image. Pointers are used for fast access.
void Image::ConvertToGreyscale()
{
	// Check to see if the image is already greyscale
	if(!IsGreyscale())
	{
		// Create a copy of the image, which will be a reference for 
		// the conversion. Destroy the old version and allocate memory
		// for a grey version.
		Image orig(*this);
		Destroy();
		Create(orig.GetWidth(),orig.GetHeight(),8);

		// Use pitch to find beginning of image in memory.
		int grey_buffer = GetPitch();
		int orig_buffer = orig.GetPitch();
		BYTE *orig_pixel = NULL, *grey_pixel = NULL, *r=NULL, *g=NULL, *b=NULL;
		if(grey_buffer > 0)
			grey_pixel = static_cast<BYTE*>(GetPixelAddress(0,0));
		else
			grey_pixel = static_cast<BYTE*>(GetPixelAddress(0,GetHeight()-1));
		if(orig_buffer > 0)
			orig_pixel = static_cast<BYTE*>(orig.GetPixelAddress(0,0));
		else
			orig_pixel = static_cast<BYTE*>(orig.GetPixelAddress(0,orig.GetHeight()-1));

		// Next, we check if there is any extra space after each line 
		// of the image.
		grey_buffer = abs(grey_buffer) - (GetWidth()*(GetBPP()>>3));
		orig_buffer = abs(orig_buffer) - (orig.GetWidth()*(orig.GetBPP()>>3));

		// Convert to grey.
		for(int i=0; i<GetHeight(); i++)
		{
			for(int j=0; j<GetWidth(); j++)
			{
				b = orig_pixel++;
				g = orig_pixel++;
				r = orig_pixel++;
				// This conversion to grey uses the NTSC standard.
				*grey_pixel++ = static_cast<BYTE>((0.299f*(*r) + 0.587f*(*g) + 0.114f*(*b)) + 0.5f);
			}
			for(int k=0; k<orig_buffer; k++)
				orig_pixel++;
			for(int k=0; k<grey_buffer; k++)
				grey_pixel++;
		}

		// For greyscale images, a palette must be specified, 
		// otherwise trying to save the image won't work.
		RGBQUAD colours[256];
		for(int i=0; i<256; i++)
		{
			colours[i].rgbRed = i;
			colours[i].rgbGreen = i;
			colours[i].rgbBlue = i;
		}
		SetColorTable(0,256,colours);
	}
}

// This function returns a greyscale version of the current image.
Image* Image::GetGreyscale() const
{
	// Create pointer for new image.
	Image* grey_img;

	// Check to see if the image is already greyscale.
	if(IsGreyscale())
	{
		grey_img = new Image(*this);
	}
	else
	{
		grey_img = new Image;
		grey_img->Create(GetWidth(),GetHeight(),8);

		// Use pitch to find beginning of image in memory.
		int buffer = GetPitch();
		int grey_buffer = grey_img->GetPitch();
		BYTE *pixel = NULL, *grey_pixel = NULL, *r=NULL, *g=NULL, *b=NULL;
		if(buffer > 0)
			pixel = static_cast<BYTE*>(const_cast<void*>(GetPixelAddress(0,0)));
		else
			pixel = static_cast<BYTE*>(const_cast<void*>(GetPixelAddress(0,GetHeight()-1)));
		if(grey_buffer > 0)
			grey_pixel = static_cast<BYTE*>(grey_img->GetPixelAddress(0,0));
		else
			grey_pixel = static_cast<BYTE*>(grey_img->GetPixelAddress(0,grey_img->GetHeight()-1));

		// Next, we check if there is any extra space after each line 
		// of the image.
		buffer = abs(buffer) - (GetWidth()*(GetBPP()>>3));
		grey_buffer = abs(grey_buffer) - (grey_img->GetWidth()*(grey_img->GetBPP()>>3));

		// Convert to grey.
		for(int i=0; i<grey_img->GetHeight(); i++)
		{
			for(int j=0; j<grey_img->GetWidth(); j++)
			{
				b = pixel++;
				g = pixel++;
				r = pixel++;
				// This conversion to grey uses the NTSC standard.
				*grey_pixel++ = static_cast<BYTE>((0.299f*(*r) + 0.587f*(*g) + 0.114f*(*b)) + 0.5f);
			}
			for(int k=0; k<buffer; k++)
				pixel++;
			for(int k=0; k<grey_buffer; k++)
				grey_pixel++;
		}

		// For greyscale images, a palette must be specified, 
		// otherwise trying to save the image won't work.
		RGBQUAD colours[256];
		for(int i=0; i<256; i++)
		{
			colours[i].rgbRed = i;
			colours[i].rgbGreen = i;
			colours[i].rgbBlue = i;
		}
		grey_img->SetColorTable(0,256,colours);
	}
	return grey_img;
}

// This function returns "true" if the image has a single channel,
// and false otherwise.
bool Image::IsGreyscale() const
{
	if((GetBPP()>>3) == 1)
		return true;
	else
		return false;
}

// This function draws a rectangle on the image in the specified
// RGB colour. If no colour is specified, the default is green.
void Image::DrawRectangle(int x, int y, int w, int h, BYTE R, BYTE G, BYTE B)
{
	// Use assertions to check input parameters. Faster than 
	// exceptions. Assertions will not run in the release
	// version.
	assert((x >= 0) && ((x+w) > x) && ((x+w) <= GetWidth()));
	assert((y >= 0) && ((y+h) > y) && ((y+h) <= GetHeight()));
	assert(R >= 0 && R <= 255);
	assert(G >= 0 && G <= 255);
	assert(B >= 0 && B <= 255);

	// Use pitch to find beginning of image in memory.
	BYTE* pixel = NULL;
	int buffer = GetPitch();
	if(buffer > 0)
		pixel = static_cast<BYTE*>(GetPixelAddress(x,y));
	else
		pixel = static_cast<BYTE*>(GetPixelAddress(x,y+h-1));

	// Next, we check if there is any extra space after each line 
	// of the image.
	buffer = abs(buffer) - (GetWidth()*(GetBPP()>>3));

	// Check if we are drawing in a colour or grey image.
	if(IsGreyscale())
	{
		BYTE grey = static_cast<BYTE>((0.299f*R + 0.587f*G + 0.114f*B) + 0.5f);
		
		// Draw the first horizontal line of the rectangle.
		for(int j=0; j<w; j++)
			*pixel++ = grey;

		// Move to the correct position for drawing the next line.
		pixel += GetWidth() - w + buffer;

		// Draw the vertical lines of the rectangle.
		for(int i=0; i<h-2; i++)
		{
			for(int j=0; j<w; j++)
				if(j==0 || j==w-1)
					*pixel++ = grey;
				else
					++pixel;
			pixel += GetWidth() - w + buffer;
		}

		// Draw the last horizontal line of the rectangle.
		for(int j=0; j<w; j++)
			*pixel++ = grey;
	}
	else
	{
		// Draw the first horizontal line of the rectangle.
		for(int j=0; j<w; j++)
		{
			*pixel++ = B;
			*pixel++ = G;
			*pixel++ = R;
		}

		// Move to the correct position for drawing the next line.
		pixel += (3*(GetWidth()-w)) + buffer;

		// Draw the vertical lines of the rectangle.
		for(int i=0; i<h-2; i++)
		{
			for(int j=0; j<w; j++)
			{
				if(j==0 || j==w-1)
				{
					*pixel++ = B;
					*pixel++ = G;
					*pixel++ = R;
				}
				else
					++++++pixel;
			}
			pixel += (3*(GetWidth()-w))+buffer;
		}

		// Draw the last horizontal line of the rectangle.
		for(int j=0; j<w; j++)
		{
			*pixel++ = B;
			*pixel++ = G;
			*pixel++ = R;
		}
	}
}

// Retrieve a particular subwindow from an image.
Image* Image::GetWindow(int x, int y, int w, int h) const
{
	// Use assertions to check input parameters. Faster than 
	// exceptions. Assertions will not run in the release
	// version.
	assert((x >= 0) && ((x+w) > x) && ((x+w) <= GetWidth()));
	assert((y >= 0) && ((y+h) > y) && ((y+h) <= GetHeight()));

	// Allocate memory for new image.
	Image* sub_img = new Image;
	sub_img->Create(w,h,GetBPP());

	// Use pitch to find beginning of image in memory.
	BYTE *pixel = NULL, *sub_pixel = NULL;
	int buffer = GetPitch();
	int sub_buffer = sub_img->GetPitch();
	if(buffer > 0)
		pixel = static_cast<BYTE*>(const_cast<void*>(GetPixelAddress(x,y)));
	else
		pixel = static_cast<BYTE*>(const_cast<void*>(GetPixelAddress(x,y+h-1)));
	if(sub_buffer > 0)
		sub_pixel = static_cast<BYTE*>(sub_img->GetPixelAddress(0,0));
	else
		sub_pixel = static_cast<BYTE*>(sub_img->GetPixelAddress(0,sub_img->GetHeight()-1));

	// Next, we check if there is any extra space after each line 
	// of the image.
	buffer = abs(buffer) - (GetWidth()*(GetBPP()>>3));
	sub_buffer = abs(sub_buffer) - (sub_img->GetWidth()*(sub_img->GetBPP()>>3));

	// Check to see if the image is greyscale.
	if(IsGreyscale())
	{
		for(int i=0; i<h; i++)
		{
			for(int j=0; j<w; j++)
				*sub_pixel++ = *pixel++;
			for(int k=0; k<buffer; k++)
				++pixel;
			for(int k=0; k<sub_buffer; k++)
				++sub_pixel;
			// Skip to the next row in the current image
			for(int j=0; j<(GetWidth()-w); j++)
				++pixel;
		}
		// For greyscale images, a palette must be specified, 
		// otherwise trying to save the image won't work.
		RGBQUAD colours[256];
		for(int i=0; i<256; i++)
		{
			colours[i].rgbRed = i;
			colours[i].rgbGreen = i;
			colours[i].rgbBlue = i;
		}
		sub_img->SetColorTable(0,256,colours);
	}
	else
	{
		for(int i=0; i<h; i++)
		{
			for(int j=0; j<w; j++)
			{
				*sub_pixel++ = *pixel++;
				*sub_pixel++ = *pixel++;
				*sub_pixel++ = *pixel++;
			}
			for(int k=0; k<buffer; k++)
				++pixel;
			for(int k=0; k<sub_buffer; k++)
				++sub_pixel;
			// Skip to the next row in the current image
			for(int j=0; j<(GetWidth()-w); j++)
				++++++pixel;
		}
	}
	return sub_img;
}

// Returns random window from an image. The input parameters are 
// the width and height of the image to be returned, which can 
// range from 1 to the full width/height. The final parameter can 
// be used to choose the window at a random scale when set to
// "true". Only larger scales are chosen.
Image* Image::GetRandomWindow(int w, int h, bool rand_scale) const
{
	// Use assertions to check input parameters. Faster than 
	// exceptions. Assertions will not run in the release
	// version.
	assert((w > 0) && (w <= GetWidth()));
	assert((h > 0) && (h <= GetHeight()));

	if(rand_scale)
	{
		// Calculate maximum possible scale factor, which is the 
		// minimum of the horizontal and vertical maximum scale 
		// factors.
		float max_scale_w = static_cast<float>(GetWidth())/w;
		float max_scale_h = static_cast<float>(GetHeight())/h;
		float max_scale = (max_scale_w < max_scale_h) ? max_scale_w : max_scale_h;

		// Generate a random scale and random top left corner.
		float rand_s = ((max_scale - 1.0f)*static_cast<float>(rand())/RAND_MAX) + 1.0f;
		int rand_w = static_cast<int>(rand_s*w + 0.5f);
		int rand_h = static_cast<int>(rand_s*h + 0.5f);
		int rand_x = rand() % (GetWidth() - rand_w + 1);
		int rand_y = rand() % (GetHeight() - rand_h + 1);

		// Return the randomly scaled random window. Also clean up
		// memory.
		Image* rand_window = GetWindow(rand_x, rand_y, rand_w, rand_h);
		Image* final_rand_window = rand_window->GetRescaledImage(w,h);
		delete rand_window;
		return final_rand_window;
	}
	else
	{
		// Generate random top left corner.
		int rand_x = rand() % (GetWidth() - w + 1);
		int rand_y = rand() % (GetHeight()- h + 1);

		// Return the random window
		return GetWindow(rand_x, rand_y, w, h);
	}
}

// This function returns an image that has been rescaled by
// nearest neighbour interpolation to the specified dimensions. 
// The output is quite similar to (possibly identical to) the 
// output from MATLAB's 'imresize' function with anti-aliasing 
// disabled. It can be used for up-scaling and down-scaling.
Image* Image::GetRescaledImage(int w, int h) const
{
	// Use assertions to check input parameters. Faster than 
	// exceptions. Assertions will not run in the release
	// version.
	assert((w > 0) && (h > 0));

	// Allocate memory for new image.
	Image* scale_img = new Image;
	scale_img->Create(w,h,GetBPP());

	// Use pitch to find beginning of image in memory.
	BYTE *pixel = NULL, *scale_pixel = NULL;
	int buffer = GetPitch();
	int scale_buffer = scale_img->GetPitch();
	if(buffer > 0)
		pixel = static_cast<BYTE*>(const_cast<void*>(GetPixelAddress(0,0)));
	else
		pixel = static_cast<BYTE*>(const_cast<void*>(GetPixelAddress(0,GetHeight()-1)));

	// Calculate ratios and offsets for use in bilinear 
	// interpolation. Note that in this instance, the pitch for 
	// the new rescaled image is very important, as it determines 
	// how the interpolation algorithm should proceed.
	float ratio_w = static_cast<float>(GetWidth())/scale_img->GetWidth();
	float offset_w = ratio_w/2 - 0.5f;
	float ratio_h = 0.0f;
	float offset_h = 0.0f;
	int nearest_y = 0;
	bool origin_bottom = true;
	if(scale_buffer > 0)
	{
		scale_pixel = static_cast<BYTE*>(scale_img->GetPixelAddress(0,0));
		ratio_h = static_cast<float>(GetHeight())/scale_img->GetHeight();
		offset_h = ratio_h/2 - 0.5f;
		nearest_y = 0;
		origin_bottom = false;
	}
	else
	{
		scale_pixel = static_cast<BYTE*>(scale_img->GetPixelAddress(0,scale_img->GetHeight()-1));
		ratio_h = static_cast<float>(-GetHeight())/scale_img->GetHeight();
		offset_h = GetHeight() - 0.5f + ratio_h/2;
		nearest_y = GetHeight()-1;
		origin_bottom = true;
	}

	// Next, we check if there is any extra space after each line 
	// of the image.
	buffer = abs(buffer) - (GetWidth()*(GetBPP()>>3));
	scale_buffer = abs(scale_buffer) - (scale_img->GetWidth()*(scale_img->GetBPP()>>3));

	// Check to see if the image is greyscale.
	if(IsGreyscale())
	{
		float y_coord = offset_h;
		int previous_y = nearest_y;
		for(int i=0; i<scale_img->GetHeight(); i++)
		{
			if(((y_coord < GetHeight() - 1) && origin_bottom) || ((y_coord > 0) && !origin_bottom))
				nearest_y = static_cast<int>(y_coord + 0.5f);

			pixel = static_cast<BYTE*>(const_cast<void*>(GetPixelAddress(0,nearest_y)));

			previous_y = nearest_y;
			y_coord = y_coord + ratio_h;

			float x_coord = offset_w;
			int nearest_x = 0, previous_x = 0;

			for(int j=0; j<scale_img->GetWidth(); j++)
			{
				if(x_coord > 0)
					nearest_x = static_cast<int>(x_coord + 0.5f);

				// Check if we have to move from the current 
				// position.
				if(nearest_x != previous_x)
					for(int k=0; k<(nearest_x-previous_x); k++)
						++pixel;

				// Set the current pixel. Use a new pointer, as
				// the old one maybe reused if the position 
				// doesn't change (the position might stay the 
				// same if we are upscaling and image).
				BYTE* current_pixel = pixel;
				*scale_pixel++ = *current_pixel++;

				previous_x = nearest_x;
				x_coord = x_coord + ratio_w;
			}
			for(int k=0; k<scale_buffer; k++)
				++scale_pixel;
		}
		// For greyscale images, a palette must be specified, 
		// otherwise trying to save the image won't work.
		RGBQUAD colours[256];
		for(int i=0; i<256; i++)
		{
			colours[i].rgbRed = i;
			colours[i].rgbGreen = i;
			colours[i].rgbBlue = i;
		}
		scale_img->SetColorTable(0,256,colours);
	}
	else
	{
		float y_coord = offset_h;
		int previous_y = nearest_y;
		for(int i=0; i<scale_img->GetHeight(); i++)
		{
			if(((y_coord < GetHeight() - 1) && origin_bottom) || ((y_coord > 0) && !origin_bottom))
				nearest_y = static_cast<int>(y_coord + 0.5f);

			pixel = static_cast<BYTE*>(const_cast<void*>(GetPixelAddress(0,nearest_y)));

			previous_y = nearest_y;
			y_coord = y_coord + ratio_h;

			float x_coord = offset_w;
			int nearest_x = 0, previous_x = 0;

			for(int j=0; j<scale_img->GetWidth(); j++)
			{
				if(x_coord > 0)
					nearest_x = static_cast<int>(x_coord + 0.5f);

				// Check if we have to move from the current 
				// position.
				if(nearest_x != previous_x)
					for(int k=0; k<(nearest_x-previous_x); k++)
						++++++pixel;

				// Set the current pixel. Use a new pointer, as 
				// the old one maybe reused if the position 
				// doesn't change (the position might stay the 
				// same if we are upscaling and image).

				BYTE* current_pixel = pixel;
				*scale_pixel++ = *current_pixel++;
				*scale_pixel++ = *current_pixel++;
				*scale_pixel++ = *current_pixel++;

				previous_x = nearest_x;
				x_coord = x_coord + ratio_w;
			}
			for(int k=0; k<scale_buffer; k++)
				++scale_pixel;
		}
	}

	return scale_img;
}

// This function returns an image that has been rescaled by 
// nearest neighbour interpolation by the specified scale factor.
// It can be used for up-scaling and down-scaling.
Image* Image::GetRescaledImage(float s) const
{
	// Use assertions to check input parameters. Faster than 
	// exceptions. Assertions will not run in the release version
	// Note that there is no check to prevent very large scale 
	// factors from being used.
	assert(s > 0);

	return GetRescaledImage(static_cast<int>(s*GetWidth() + 0.5f),
							static_cast<int>(s*GetHeight() + 0.5f));
}
